

import java.util.ArrayList;
import DataBaseManager.*;
import DataBaseQuery.AndCondition;
import DataBaseQuery.CompareCondition;
import DataBaseQuery.Condition;
import DataBaseQuery.ConstantCondition;
import DataBaseQuery.GroupView;
import DataBaseQuery.MyConstant;
import DataBaseQuery.MyExpression;
import DataBaseQuery.MyFunction;
import DataBaseQuery.MyVarible;
import DataBaseQuery.NotCondition;
import DataBaseQuery.OrCondition;
import DataBaseQuery.RegularView;
import DataBaseQuery.VariableReference;
import DataBaseQuery.View;
import DataType.MyDataType;
import DataType.MyNumberic;
import DataType.MyString;

public class Query {

	DataBase dataBase;
	String[] errorBase = new String[] { "Gramar", "Table doesn't exists",
			"Field doesn't exists", "Miss datatype", "Unknow varible",
			"Cann't execute" };
	int error = 0;
	String messenger="";

	public Query(DataBase dataBase) {
		this.dataBase = dataBase;
	}

	private boolean checkformat(String[] inputs) {
		for (String string : inputs) {
			if (!checkformat(string)) {
				return false;
			}
		}
		return true;
	}

	private boolean checkformat(String input) {
		if(input.length()==0)
			return false;
		String symbol = ";,.(";
		boolean openString = false;
		int open = 0;

		char lastchar = input.charAt(0);
		for (int i = 0; i < input.length(); i++) {
			char chr = input.charAt(i);
			if (chr == '(' && !openString) {
				open++;
			} else if (chr == ')' && !openString) {

				if (open == 0)
					return false;
				open--;
			} else if (chr == '\'') {

				if (openString) {
					openString = false;
				} else {
					openString = true;
				}
			} else if (symbol.indexOf(chr) != -1 && !openString) {

				if (symbol.indexOf(lastchar) != -1) {
					return false;
				}
			} else if ("{}[]".indexOf(chr) != -1 && !openString) {
				return false;
			}
			if (chr != ' ') {

				lastchar = chr;
			}

		}
		return (!openString && open == 0);
	}

	private void setError(int er) {
		error = er;

		System.out.print("Error : ");
		System.out.println();
		messenger = "Error : "+ errorBase[er];
	}

	private String format(String input) {
		String output;
		input = input.replaceAll("\n", " ");
		input = input.trim();//.toLowerCase();
		char[] buffer = new char[1024];
		int k = 0;
		char lastChar = input.charAt(0);
		boolean openString = false;
		String exSymbol = ";,*+()";

		//boolean isNumber = false;
		for (int i = 0; i < input.length(); i++) {
			char chr = input.charAt(i);
			if (chr == '\'') {
				openString = !openString;
			}
			if (openString) {
				buffer[k++] = chr;
				lastChar = chr;
				continue;
			}
			if (chr == ' ') {
				if (lastChar != ' ') {
					buffer[k++] = chr;
				}
			} else if (chr == '(' || chr == ')') {
				if (lastChar != ' ' && exSymbol.indexOf(lastChar) == -1) {
					buffer[k++] = ' ';
				}
				buffer[k++] = chr;
			} else {
				if (exSymbol.indexOf(lastChar) != -1) {
					buffer[k++] = ' ';
				}
				
				buffer[k++] = Character.toLowerCase(chr);
			}
			lastChar = chr;
		}

		output = new String(buffer, 0, k);

		return output;
	}

	private String secondaryFormat(String input) {

		if(!checkformat(input))
			return "";
		input = input.trim();
		if (input.length() == 0)
			return "";
		String output;

		//int k = 0;
		//char lastChar = input.charAt(0);
		String exSymbol = ";,.() ";
		int nOpen = 0;
		int nClose = 0;
		int begin = 0;
		int end = input.length() - 1;

		while (exSymbol.indexOf(input.charAt(begin)) != -1) {
			if (input.charAt(begin) == '(') {
				nOpen++;
			} else if (input.charAt(begin) == ')') {
				if (nOpen > 0)
					nOpen--;
			}
			begin++;
		}
		while (exSymbol.indexOf(input.charAt(end)) != -1) {
			if (input.charAt(end) == ')') {
				nClose++;
			} else if (input.charAt(end) == '(') {
				if (nClose > 0)
					nClose--;
			}
			end--;
		}

		output = input.substring(begin, end + 1);

		int maxClose = 0;
		int close = 0;
		for (int i = 0; i < output.length(); i++) {
			char ch = output.charAt(i);
			if (ch == '(') {
				close--;
			} else if (ch == ')') {
				close++;
				if (close > maxClose) {
					maxClose = close;
				}
			}
		}
		int d = nOpen - maxClose;
		nClose -= d;
		nOpen -= d;
		int i;
		char[] buffer = new char[256];
		for (i = 0; i < nOpen; i++) {
			buffer[i] = '(';
		}
		buffer[i] = '\0';
		output = new String(buffer, 0, i) + output;
		for (i = 0; i < nClose; i++) {
			buffer[i] = ')';
		}
		output = output + new String(buffer, 0, i);
		return output;
	}

	private String[] slideLastStringIn(String input, String[] strs) {
		String[] slidedStrings = new String[3];
		int[] index = new int[strs.length];
		for (int i = 0; i < index.length; i++) {
			index[i] = strs[i].length() - 1;
		}

		int open = 0;
		boolean openString = false;
		for (int i = input.length() - 1; i >= 0; i--) {
			char chr = input.charAt(i);
			if (chr == '\'') {
				openString = !openString;
			}
			if (openString)
				continue;
			if (chr == '(') {
				open++;

			} else if (chr == ')') {
				open--;
			} else if (open == 0) {
				for (int j = 0; j < strs.length; j++) {
					if (strs[j].charAt(index[j]) == chr) {
						if (index[j] == 0) {
							slidedStrings[0] = input.substring(0, i);
							slidedStrings[1] = input.substring(i,
									i + strs[j].length());
							slidedStrings[2] = input.substring(i
									+ strs[j].length());
							return slidedStrings;
						}
						index[j]--;
					} else {
						index[j] = strs[j].length() - 1;
					}
				}

			}
		}
		slidedStrings[0] = input;
		slidedStrings[1] = "";
		slidedStrings[2] = "";
		return slidedStrings;
	}

	MyDataType getDataFromString(String input) {
		input = input.trim();
		boolean hasPoint = false;
		if (input.length() == 0)
			return null;
		char firstChar = input.charAt(0);
		char lastChar = firstChar;
		if (firstChar == '\'') {
			int lastQuote = input.lastIndexOf(firstChar);
			if (lastQuote < 1)
				return null;
			String value = input.substring(1, lastQuote);
			return new MyString(value);
		} else if (firstChar == '-' || (firstChar >= '0' && firstChar <= '9')) {
			for (int i = 1; i < input.length(); i++) {
				char chr = input.charAt(i);
				if (chr == '.') {
					if (lastChar == '-' || hasPoint || i == input.length() - 1) {
						return null;
					}
					hasPoint = true;
				} else if (chr < '0' || chr > '9') {
					return null;
				}
				lastChar = chr;
			}

			return new MyNumberic(input);
		}
		return null;
	}

	MyExpression getExpressionFromString(String input) {
		input = secondaryFormat(input);
		String[] strs = slideLastStringIn(input, MyFunction.functionNames);

		for(int i=0;i< MyFunction.functionNames.length;i++)
		{
			if(strs[1].equalsIgnoreCase(MyFunction.functionNames[i]))
			{
				if(!strs[0].equals(""))
					return null;
				MyExpression var = getExpressionFromString(strs[2]);
				if(var.getType() != MyExpression.ExpressionType.VAR)
					return null;
				return new MyFunction(MyFunction.functionNames[i], (MyVarible)var);
			}
		}
		
		MyDataType value = getDataFromString(input);
		if (value != null) {
			return new MyConstant(value);
		}
		if (isValidName(input)) {
			return new MyVarible(input);
		}
		return null;
	}

	private Condition getConditionFromString(String input) {

		Condition condition;
		input = secondaryFormat(input);

		String[] clauses = slideLastStringIn(input, new String[] { " or ",
				" and " });

		if (clauses[1].equalsIgnoreCase(" or ")) {
			Condition LeftClasue = getConditionFromString(clauses[0]);
			Condition RightClause = getConditionFromString(clauses[2]);
			if (LeftClasue != null && RightClause != null) {
				condition = new OrCondition();
				((OrCondition) condition).add(LeftClasue);
				((OrCondition) condition).add(RightClause);
				return condition;
			}
		}
		if (clauses[1].equalsIgnoreCase(" and ")) {
			Condition LeftClasue = getConditionFromString(clauses[0]);
			Condition RightClause = getConditionFromString(clauses[2]);
			if (LeftClasue != null && RightClause != null) {
				condition = new AndCondition();
				((AndCondition) condition).add(LeftClasue);
				((AndCondition) condition).add(RightClause);
				return condition;
			}
		}

		if (input.indexOf("not ") == 0) {
			return new NotCondition(getConditionFromString(input.substring(4)));
		}
		return getCopareConditionFromString(input);

	}

	private boolean isValidName(String name) {
		char[] invalidSymbol = new char[] { ' ', '?', ',', '%' };
		for (int i = 0; i < invalidSymbol.length; i++) {
			if (name.indexOf(invalidSymbol[i]) != -1)
				return false;
		}
		return (name.length() > 0);
	}

	private Condition getCopareConditionFromString(String input) {
		int compareIndex;
		
		compareIndex = input.indexOf("<=");
		if (compareIndex != -1) {
			MyExpression leftExpr = getExpressionFromString(input.substring(0,
					compareIndex));
			MyExpression rightExpr = getExpressionFromString(input
					.substring(compareIndex + 2));

			if (leftExpr != null && rightExpr != null) {
				return new CompareCondition(leftExpr,
						CompareCondition.MyCompare.LESS_THAN_OR_EQUAL,
						rightExpr);
			}
		}
		compareIndex = input.indexOf(">=");
		if (compareIndex != -1) {
			MyExpression leftExpr = getExpressionFromString(input
					.substring(compareIndex + 2));
			MyExpression rightExpr = getExpressionFromString(input.substring(0,
					compareIndex));

			if (leftExpr != null && rightExpr != null) {
				return new CompareCondition(leftExpr,
						CompareCondition.MyCompare.LESS_THAN_OR_EQUAL,
						rightExpr);
			}
		}
		compareIndex = input.indexOf("<>");
		if (compareIndex != -1) {
			MyExpression leftExpr = getExpressionFromString(input.substring(0,
					compareIndex));
			MyExpression rightExpr = getExpressionFromString(input
					.substring(compareIndex + 2));

			if (leftExpr != null && rightExpr != null) {
				return new CompareCondition(leftExpr,
						CompareCondition.MyCompare.NOT_EQUAL, rightExpr);
			}
		}
		compareIndex = input.indexOf("=");
		if (compareIndex != -1) {
			MyExpression leftExpr = getExpressionFromString(input.substring(0,
					compareIndex));
			MyExpression rightExpr = getExpressionFromString(input
					.substring(compareIndex + 1));

			if (leftExpr != null && rightExpr != null) {
				return new CompareCondition(leftExpr,
						CompareCondition.MyCompare.EQUAL, rightExpr);
			}
		}
		compareIndex = input.indexOf("<");
		if (compareIndex != -1) {
			MyExpression leftExpr = getExpressionFromString(input.substring(0,
					compareIndex));
			MyExpression rightExpr = getExpressionFromString(input
					.substring(compareIndex + 1));

			if (leftExpr != null && rightExpr != null) {
				return new CompareCondition(leftExpr,
						CompareCondition.MyCompare.LESS_THAN, rightExpr);
			}
		}
		compareIndex = input.indexOf(">");
		if (compareIndex != -1) {

			MyExpression leftExpr = getExpressionFromString(input
					.substring(compareIndex + 1));
			MyExpression rightExpr = getExpressionFromString(input.substring(0,
					compareIndex));

			if (leftExpr != null && rightExpr != null) {
				return new CompareCondition(leftExpr,
						CompareCondition.MyCompare.LESS_THAN, rightExpr);
			}
		}

		return null;
	}

	public DTO execQuery(String queryString) {

		// -----------format and shit --------
		messenger = "";
		String formatedString = format(queryString);
		if (!checkformat(formatedString)) {
			setError(0);
			return DTO.CreateDTO(messenger);
		}

		// -----------done with format --------
		String[] strs = formatedString.split(" ", 2);
		if (strs.length < 2) {
			setError(0);
			return DTO.CreateDTO(messenger);
		}
		String conmand = strs[0];

		// ---------------Start-----
		

		
		if (conmand.equals("select")) {

			DTO dto = new DTO();
			dto = Table2DTO(selectQuery(queryString));
			dto.messenger = messenger;
			
			return dto;
		}
		else
		{
			if (conmand.equals("insert")) {
				insert(strs[1]);
				
			}
			if (conmand.equals("delete")) {
				delete(strs[1]);
			}
			if (conmand.equals("update")) {
				update(strs[1]);
			}
			return DTO.CreateDTO(messenger);
		}
	}

	private void insert(String queryString) {

		if (queryString.indexOf("into ") != 0) {
			setError(0);
			return;
		}
		queryString = queryString.substring(5);

		String[] strs = queryString.split(" values ", 3);

		if (strs.length != 2 || !checkformat(strs)) {
			setError(0);
			return;
		}

		String tableName;
		String fieldsString = new String();
		Boolean hasFields = true;
		int open = strs[0].indexOf('(');
		int close = strs[0].lastIndexOf(')');
		if (open == -1 || close == -1) {
			tableName = strs[0];
			hasFields = false;
		} else {
			tableName = strs[0].substring(0, open - 1);
			fieldsString = secondaryFormat(strs[0].substring(open + 1, close));
		}

		if (!isValidName(tableName)) {
			setError(0);
			return;
		}
		Table table = dataBase.getTable(tableName);
		if (table == null) {
			setError(1);
			return;
		}
		Row dataRow = table.createNewDataRow();

		open = strs[1].indexOf('(');
		close = strs[1].lastIndexOf(')');
		if (open == -1 || close == -1) {
			setError(0);
			return;
		}
		String valuesString = secondaryFormat(strs[1]
				.substring(open + 1, close));

		String[] fields = fieldsString.split(",");
		String[] values = valuesString.split(",");

		if (hasFields) {
			for (int i = 0; i < fields.length; i++) {
				fields[i] = fields[i].trim();

				int index = table.fieldIndex(fields[i]);
				if (index == -1) {
					setError(2);
					return;
				}
				MyDataType value = getDataFromString(values[i]);

				if (value == null) {
					setError(0);
					return;
				}

				if (value.getType() != table.getFields().get(index).getType()) {
					setError(3);
					return;
				}
				dataRow.setValue(index, value);
			}
		} else {
			for (int i = 0; i < values.length; i++) {
				MyDataType value = getDataFromString(values[i]);
				if (value == null) {
					setError(0);
					return;
				}

				if (value.getType() != table.getFields().get(i).getType()) {
					setError(3);
					return;
				}
				dataRow.setValue(i, value);
			}
		}
		if(table.insertData(dataRow)==-1)
		{
			System.out.println("1 row effect");
			messenger = "1 row effect";
			return;
		}
		setError(5);
	}

	private void delete(String queryString) {
		if (queryString.indexOf("from ") != 0) {
			setError(0);
			return;
		}
		queryString = queryString.substring(5);

		String[] strs = queryString.split(" where ", 3);

		if (strs.length > 2 || !checkformat(strs)) {
			setError(0);
			return;
		}
		int count = 0;

		String tableName = strs[0].trim();
		if (!isValidName(tableName)) {
			setError(0);
			return;
		}
		Table table = dataBase.getTable(tableName);
		if (table == null) {
			setError(1);
			return;
		}

		if (strs.length == 1) {
			
			count = table.getNumberOfRow();
			table.clearData();
		} else {
			Condition condition = getConditionFromString(strs[1]);
			if (condition == null) {
				setError(0);
				return;
			}
			int i = 0;
			while (i < table.getNumberOfRow()) {

				MyVarible.clearCurrentVarible();
				MyVarible.addCurrentVarible(VariableReference
						.getVaribleRefFromTable(table, i));

				if (!condition.checkInvalidVarible()) {
					setError(4);
					return;
				}
				if (condition.isTrue()) {
					table.deleteDataRow(i);
					count++;
				} else {
					i++;
				}
			}
		}
		System.out.println(String.valueOf(count) + " row effect");
		messenger = String.valueOf(count) + " row effect";

	}

	private void update(String queryString) {
		String[] tableAndSet = queryString.split(" set ", 3);

		if (tableAndSet.length != 2 || !checkformat(tableAndSet)) {
			setError(0);
			return;
		}
		String tableName = tableAndSet[0].trim();
		if (!isValidName(tableName)) {
			setError(0);
			return;
		}
		Table table = dataBase.getTable(tableName);
		if (table == null) {
			setError(1);
			return;
		}
		int count = 0;
		int n = table.getNumberOfRow();
		String[] strs = tableAndSet[1].split(" where ", 3);
		if (strs.length > 2 || !checkformat(strs)) {
			setError(0);
			return;
		}
		String[] setsString = strs[0].split(",");
		ArrayList<VariableReference> updateSets = new ArrayList<VariableReference>();
		for (int i = 0; i < setsString.length; i++) {
			int index = setsString[i].indexOf("=");
			if (index == -1) {
				setError(0);
				return;
			}
			MyDataType value = getDataFromString(setsString[i]
					.substring(index + 1));
			String fieldName = setsString[i].substring(0, index).trim();
			updateSets.add(new VariableReference(fieldName, value));
		}
		Condition condition;
		if (strs.length == 2) {
			condition = getConditionFromString(strs[1]);
			if (condition == null) {
				setError(0);
				return;
			}
		} else
			condition = new ConstantCondition(true);

		for (int i = 0; i < n; i++) {
			MyVarible.clearCurrentVarible();
			MyVarible.addCurrentVarible(VariableReference
					.getVaribleRefFromTable(table, i));

			if (!condition.checkInvalidVarible()) {
				setError(4);
				return;
			}
			if (condition.isTrue()) {
				boolean flag = false;
				for (int j = 0; j < updateSets.size(); j++) {
					if(table.setData(updateSets.get(j).fieldName, i,updateSets.get(j).value))
						flag = true;
				}
				if(flag)
					count++;
			} 
		}

		System.out.println(String.valueOf(count) + " row effect");
		messenger = String.valueOf(count) + " row effect";
	}

	public Table selectQuery(String input) {
		input = format(input);
		RegularView v = getViewFromSelectString(input);
		if (v == null ) {
			setError(5);
			return new Table("");
		}
		messenger = "Success";
		return v.toTable();
	}
	private RegularView getViewFromSelectString(String input) {

		if (!checkformat(input)) {
			return null;
		}

		input = secondaryFormat(input);
		int selectIndex = input.indexOf("select ");
		if(selectIndex == 0)
		{
			input = input.substring(7);
			String[] strsSelect = slideLastStringIn(input, new String[]{" from "});
			if(strsSelect[1].equals(" from "))
			{
				View v = getRawView(strsSelect[2]);
				if (v != null) {
	
					
					String colsString = strsSelect[0];
	
					ArrayList<MyExpression> listFields = new ArrayList<MyExpression>();
					String[] colStrings = colsString.split(",");
					for (int i = 0; i < colStrings.length; i++) {
						listFields.add(getExpressionFromString(colStrings[i].trim()));
					}
					return v.select(listFields);
				}
			}
		}
		return null;
	}
	private View getRawView(String input) {

		if (!checkformat(input)) {
			return null;
		}

		input = secondaryFormat(input);

		
		String[] strsFrom = slideLastStringIn(input, new String[]{" where "," group by "," having "});
		// int orderIndex = input.indexOf(" order by ");
		if(strsFrom[1].equals(" where "))
		{
			RegularView v = (RegularView)getRawView(strsFrom[0]);
			if (v == null)
				return v;
			Condition con = getConditionFromString(strsFrom[2]);
			if(con != null)
			{
				return v.choice(con);
			}
		}
		else if(strsFrom[1].equals(" group by "))
		{
			String[] strs = strsFrom[2].split(",");
			ArrayList<String> fieldNames = new ArrayList<String>();
			for (String string : strs) {
				fieldNames.add(string.trim());
			}
			RegularView v = (RegularView)getRawView(strsFrom[0]);
			if (v == null)
				return v;
			GroupView gView = v.group(fieldNames);
			return gView;
		}
		else if(strsFrom[1].equals(" having "))
		{
			GroupView v = (GroupView)getRawView(strsFrom[0]);
			if (v == null)
				return v;
			Condition con = getConditionFromString(strsFrom[2]);
			if(con != null)
			{
				return v.choice(con);
			}
		}
		else
		{
			//input = input.substring(5);
			String[] viewStrings = input.split(",");

			if (viewStrings.length < 1) {
				return null;
			}

			ArrayList<String> tables = new ArrayList<String>();
			Table table = dataBase.getTable(viewStrings[0].trim());

			RegularView view = new RegularView(1);
			for (int i = 0; i < viewStrings.length; i++) {
				String[] strs = viewStrings[i].trim().split(" ", 2);
				String tableName;
				String newName;
				if (strs.length == 0
						|| (strs.length == 2 && !isValidName(strs[1].trim())))
					return null;
				else if (strs.length == 1) {
					tableName = strs[0].trim();
					table = dataBase.getTable(tableName);

					for (String name : tables) {
						if (tableName.equalsIgnoreCase(name)) {
							return null;
						}
					}
					tables.add(tableName);

					if (table == null)
						return null;

					view = view.mult(new RegularView(table));
				} else {
					tableName = strs[0].trim();
					newName = strs[1].trim();
					if (!isValidName(newName)) {
						return null;
					}

					table = dataBase.getTable(tableName);
					if (table == null)
						return null;

					for (String name : tables) {
						if (tableName.equalsIgnoreCase(name)) {
							return null;
						}
					}
					tables.add(tableName);

					if (newName != tableName) {
						for (String name : tables) {
							if (newName.equalsIgnoreCase(name)) {
								return null;
							}
						}
						tables.add(newName);

						view = view.mult(new RegularView(table, newName));
					} else {
						view = view.mult(new RegularView(table));
					}
				}

			}

			return view;
		}

		return null;
	}
	private DTO Table2DTO(Table table)
	{
		DTO dto = new DTO();
		int n = table.getFields().size();
		int m = table.getNumberOfRow();
		dto.fieldNames = new String[n];
		dto.data = new String[m][];
		for(int i=0;i<m;i++)
		{
			dto.data[i] = new String[n];
			for(int j=0;j<n;j++)
			{
				dto.data[i][j] = table.getValue(j, i).toString();
			}
		}
		for(int i=0;i<n;i++)
		{
			dto.fieldNames[i] = table.getFields().get(i).getName();
			/*dto.data[i] = new String[m];
			for(int j=0;j<m;j++)
			{
				dto.data[i][j] = table.getValue(i, j).toString();
			}*/
		}
		return dto;
	}
	/*
	 * public View getViewFromViewsString(String input) { String[] viewStrings =
	 * input.split(",");
	 * 
	 * if (viewStrings.length < 1) { return null; } Table table =
	 * dataBase.getTable(viewStrings[0]); if (table == null) return null; View
	 * view = new View(table); for (int i = 1; i < viewStrings.length; i++) {
	 * table = dataBase.getTable(viewStrings[0]); if (table == null) return
	 * null; view = view.mult(new View(table)); }
	 * 
	 * return view; }
	 */
}
